package com.autonavi.yunda.yunji.core.engine;

import com.autonavi.yunda.yunji.common.utils.JsonUtils;
import com.autonavi.yunda.yunji.core.engine.context.EngineContext;
import com.autonavi.yunda.yunji.core.engine.context.EngineTransactionContext;
import com.autonavi.yunda.yunji.core.engine.context.NodeContext;
import com.autonavi.yunda.yunji.core.enums.AccessStatus;
import com.autonavi.yunda.yunji.core.service.dbfactory.DbFactoryService;
import com.autonavi.yunda.yunji.core.vo.TransactionConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import static com.autonavi.yunda.yunji.core.config.EngineConstant.PARAM_VALIDATE_FUN_NAME;

/**
 * @author cizhong.wcs
 * @date 2022/5/31 11:50
 * 串行执行引擎
 */
@Slf4j
@Component
public class SerialEngineWorker extends AbstractEngineWorker {
    private static final Logger engineLogger = LoggerFactory.getLogger("logger.engine.exec.log");
    @Resource
    private DbFactoryService dbFactoryService;


    public void doExe(EngineContext engineContext, Map<String, Object> inputParams) {
        //初始化SQL事务
        initTransactionContext(engineContext);
        try {
            engineContext.startTime = System.currentTimeMillis();
            // 给属性赋值
            engineContext.injectParams(inputParams);
            // 执行输入校验逻辑
            engineContext.invoke(PARAM_VALIDATE_FUN_NAME);
            LinkedList<Pair<NodeContext, AccessStatus>> nodeExecutingQueue = new LinkedList<>();
            nodeExecutingQueue.add(Pair.of(engineContext.getStartContext(), AccessStatus.ACCESSIBLE));
            while (!nodeExecutingQueue.isEmpty() && engineContext.finishedStatus == 0) {
                Pair<NodeContext, AccessStatus> poll = nodeExecutingQueue.poll();
                NodeContext nodeContext = poll.getLeft();
                AccessStatus accessStatus = poll.getRight();
                exeSync(nodeContext, accessStatus);
                // 检查是否执行失败
                if (nodeContext.failCheck()) {
                    break;
                }
                // 获取下一个节点
                nodeExecutingQueue.addAll(fetchNextNodes(nodeContext));
            }
        } finally {
            engineContext.endTime = System.currentTimeMillis();
            engineContext.executeTime = engineContext.endTime - engineContext.startTime;
            if (engineContext.debug) {
                engineLogger.info("exe end engineContext :{}", JsonUtils.toString(engineContext));
            }
            engineContext.handleTransactionResult();
        }
    }

    public void initTransactionContext(EngineContext engineContext) {
        List<Pair<PlatformTransactionManager, TransactionStatus>> transactions = new ArrayList<>();
        for (TransactionConfig config : engineContext.scriptClass.engineData.engineConfig.transactionConfigs) {
            PlatformTransactionManager tx = dbFactoryService.getTransactionManager(config.dbName);
            if (tx != null) {
                DefaultTransactionDefinition def = new DefaultTransactionDefinition();
                def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
                TransactionStatus status = tx.getTransaction(def);
                transactions.add(Pair.of(tx, status));
            }
        }
        engineContext.transactionContext = new EngineTransactionContext(transactions);
    }

    private void exeSync(NodeContext context, AccessStatus accessible) {
        engineLogger.info("node {} exe start", context.getName());
        checkAndExecNode(context, accessible);
        context.setEndTime(System.currentTimeMillis());
        context.setExecuteTime(context.getEndTime() - context.getStartTime());
        engineLogger.info("node {} exe end", context.getName());
    }
}
